/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.zbeboy.resource.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.JsonParser;
import org.springframework.security.oauth2.common.util.JsonParserFactory;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.approval.InMemoryApprovalStore;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author Joe Grandja
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter:off
        clients.inMemory()
                .withClient("messaging-client")
                .authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "password")
                .scopes("message.read", "message.write")
                .secret("{noop}secret")
                .accessTokenValiditySeconds(100)// 有效期
                .redirectUris("http://localhost:8080/authorized");
        // @formatter:on
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(this.authenticationManager)
                .tokenStore(tokenStore())
                .userApprovalHandler(userApprovalHandler())
                .accessTokenConverter(accessTokenConverter());
    }

    @Bean
    public UserApprovalHandler userApprovalHandler() {
        ApprovalStoreUserApprovalHandler userApprovalHandler = new ApprovalStoreUserApprovalHandler();
        userApprovalHandler.setApprovalStore(approvalStore());
        userApprovalHandler.setClientDetailsService(this.clientDetailsService);
        userApprovalHandler.setRequestFactory(new DefaultOAuth2RequestFactory(this.clientDetailsService));
        return userApprovalHandler;
    }

    @Bean
    public TokenStore tokenStore() {
        JwtTokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
        tokenStore.setApprovalStore(approvalStore());
        return tokenStore;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {

        return new JwtAccessTokenConverter() {
            private final JsonParser objectMapper = JsonParserFactory.create();
            private final Algorithm algorithm = Algorithm.HMAC256("secret");

            @Override
            protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                String token;
                try {
                    String content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
                    Map<String, Object> map = objectMapper.parseMap(content);

                    JWTCreator.Builder builder = JWT.create();

                    /*if (map.containsKey("user_name")) {
                        builder.withClaim("user_name", (String) map.get("user_name"));
                    }

                    if (map.containsKey("exp")) {
                        builder.withClaim("exp", (Integer) map.get("exp"));
                    }

                    if (map.containsKey("ati")) {
                        builder.withClaim("ati", (String) map.get("ati"));
                    }

                    if (map.containsKey("jti")) {
                        builder.withClaim("jti", (String) map.get("jti"));
                    }

                    if (map.containsKey("client_id")) {
                        builder.withClaim("client_id", (String) map.get("client_id"));
                    }

                    if (map.containsKey("authorities") && Objects.nonNull(map.get("authorities"))) {
                        ArrayList<String> authoritiesList = (ArrayList<String>) map.get("authorities");
                        String[] authoritiesArr = new String[authoritiesList.size()];
                        authoritiesList.toArray(authoritiesArr);

                        builder.withArrayClaim("authorities", authoritiesArr);
                    }

                    if (map.containsKey("scope") && Objects.nonNull(map.get("scope"))) {
                        ArrayList<String> scopeList = (ArrayList<String>) map.get("scope");
                        String[] scopeArr = new String[scopeList.size()];
                        scopeList.toArray(scopeArr);

                        builder.withArrayClaim("scope", scopeArr);
                    }*/

                    Set<Map.Entry<String, Object>> set = map.entrySet();
                    for (Map.Entry<String, Object> s : set) {
                        if (s.getValue() instanceof String) {
                            builder.withClaim(s.getKey(), (String) s.getValue());
                        } else if (s.getValue() instanceof Integer) {
                            builder.withClaim(s.getKey(), (Integer) s.getValue());
                        } else if (s.getValue() instanceof ArrayList) {
                            builder.withClaim(s.getKey(), (ArrayList<String>) s.getValue());
                        }
                    }

                    token = builder
                            .withIssuer("auth0")
                            .sign(algorithm);
                } catch (Exception ex) {
                    throw new IllegalStateException("Cannot convert access token to JSON", ex);
                }

                return token;
            }

            @Override
            protected Map<String, Object> decode(String token) {
                Map<String, Object> claims;
                try {
                    JWTVerifier verifier = JWT.require(algorithm)
                            .withIssuer("auth0")
                            .build(); //Reusable verifier instance
                    DecodedJWT jwt = verifier.verify(token);
                    Map<String, Object> map = new HashMap<>();

                    if (!jwt.getClaim("user_name").isNull()) {
                        map.put("user_name", jwt.getClaim("user_name").asString());
                    }

                    if (!jwt.getClaim("exp").isNull()) {
                        map.put("exp", jwt.getClaim("exp").asInt());
                    }

                    if (!jwt.getClaim("ati").isNull()) {
                        map.put("ati", jwt.getClaim("ati").asString());
                    }

                    if (!jwt.getClaim("jti").isNull()) {
                        map.put("jti", jwt.getClaim("jti").asString());
                    }

                    if (!jwt.getClaim("client_id").isNull()) {
                        map.put("client_id", jwt.getClaim("client_id").asString());
                    }

                    if (!jwt.getClaim("authorities").isNull()) {
                        map.put("authorities", jwt.getClaim("authorities").asArray(String.class));
                    }

                    if (!jwt.getClaim("scope").isNull()) {
                        map.put("scope", jwt.getClaim("scope").asArray(String.class));
                    }

                    String claimsStr = this.objectMapper.formatMap(map);

                    claims = this.objectMapper.parseMap(claimsStr);
                    if (claims.containsKey("exp") && claims.get("exp") instanceof Integer) {
                        Integer intValue = (Integer) claims.get("exp");
                        claims.put("exp", Long.valueOf(intValue));
                    }
                    this.getJwtClaimsSetVerifier().verify(claims);
                    return claims;
                } catch (Exception e) {
                    throw new InvalidTokenException("Cannot convert access token to JSON", e);
                }
            }
        };
    }

    @Bean
    public ApprovalStore approvalStore() {
        return new InMemoryApprovalStore();
    }

}